//订阅
class Dep {
    constructor() {
        this.subs = []
    }
    // 订阅操作
    addSub(Watcher) {//添加watch
        this.subs.push(Watcher);
    }
    // 发布操作
    notify() {
        this.subs.forEach(Watcher => {
            Watcher.update();
        })
    }
}

//观察者(发布订阅)
class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        //默认存放一个老值
        this.oldValue = this.get();
    }

    get() {
        //先把自己放在this上 
        Dep.target = this;
        //取值把数据 和这个观察者关联起来
        let value = ComplieUtil.getVal(this.vm, this.expr);
        //不取消 任何值都让取消 都会添加watcher
        Dep.target = null;
        return value;
    }
    update() {
        //更新数据 变化后调用
        let newVal = ComplieUtil.getVal(this.vm, this.expr);
        if (newVal !== this.oldValue) {
            this.cb(newVal);
        }
    }
}

// 将数据属性变成访问器属性
class Observer {//数据劫持
    constructor(data) {
        this.observer(data);
    }
    observer(data) {
        //如果是对象才观察
        if (data && typeof data === 'object') {
            for (let key in data) {
                this.defineReactive(data, key, data[key]);
            }
        }   
    }
    // data: {name:'' ,classroom: { 2010A: { name: 'xxx' }  } } 
    defineReactive(obj, key, value) {
        this.observer(value);
        let dep = new Dep();//给每个属性都加上一个dep
        //console.log(Dep.target)
        Object.defineProperty(obj, key, {
            get() {
                //创建watcher时 会取到相应的内容 并把watcher放到全局上
                // 订阅每个观察者 Dep.target = new Watcher()
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newVal) => {
                if (newVal !== value) {
                    this.observer(newVal)
                    value = newVal;
                    // 发布内容给页面进行渲染更新
                    dep.notify();
                }
            }
        })
    }
}

//基类调度
class Complie {
    // v-
    // {{}}
    constructor(el, vm) {
        //判断el属性是不是一个元素 如果不是元素 那就获取它
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        //把当前节点中的元素放到内存中
        let fragment = this.node2fragment(this.el);
        //把节点中的内容进行替换
        //用数据编译模板
        this.compile(fragment);
        //把内容塞到页面中
        this.el.appendChild(fragment)
    }
    //判断是否为指令
    isDirective(attrName) {
        return attrName.startsWith('v-')
    }
    //编译元素
    compileElement(node) {
        //拿到当前的属性
        let attributes = node.attributes; //伪数组
        [...attributes].forEach(attr => {//type = "text" v-model="schoole.name"
            //判断有没有v-model属性
            let { name, value: expr } = attr;
            console.log(expr)
            //判断是不是指令
            if (this.isDirective(name)) {
                // v-model v-text
                let [, directive] = name.split('-');
                let [directiveName, eventName] = directive.split(":")
                //需要调用不同的指令来编译
                //console.log(expr)
                ComplieUtil[directiveName](node, expr, this.vm, eventName)
            }
        })
    }
    //编译文本的
    compileText(node) {
        //判断当前文本节点中的内容是否包含{{}}
        let content = node.textContent;
        // console.log(content)
        if (/\{\{(.+?)\}\}/.test(content)) {
            //console.log(content,'text');//找到所有文本元素
            //文本节点
            // console.log(content)
            ComplieUtil['text'](node, content, this.vm);
        }
    }


    //核心的编译方法
    compile(node) {//用来编译内存的dom节点
        let childNodes = node.childNodes;
        [...childNodes].forEach(child => {
            //判断是否为节点
            if (this.isElementNode(child)) {
                this.compileElement(child);
                //如果是元素的话 需要传入自己然后遍历子节点
                this.compile(child)
            } else {
                // console.log(child)
                this.compileText(child);
            }
        });
    }

    //把每个节点都放入到内存中
    node2fragment(node) {
        //创建一个文档碎片
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = node.firstChild) {
            //appendChild具有移动性
            fragment.appendChild(firstChild)
        }
        console.log(fragment,"| 文档碎片");
        return fragment
    }
    isElementNode(node) {//是不是元素节点
        return node.nodeType === 1;
    }
}

ComplieUtil = {
    //根据表达式取到对应的数据
    getVal(vm, expr) {// 'school.name'
        return expr.split('.').reduce((data, current) => {
            console.log(data)
            return data[current];
        }, vm.$data)
    },

    setVal(vm, expr, value) {
        expr.split('.').reduce((data, current, index, arr) => {
            if (index === arr.length - 1) {
                return data[current] = value;
            }
            return data[current];
        }, vm.$data)
    },
    on(node, expr, vm, eventName) {
        console.log(vm[expr])
        node.addEventListener(eventName, (e) => {
            vm[expr]()
        })
    },

    //解析v-model指令
    model(node, expr, vm) {//node是节点 expr是表达式 vm是当前实例
        //给输入框输入value属性
        let value = this.getVal(vm, expr);
        let fn = this.updater['modelUpdater'];
        new Watcher(vm, expr, (newVal) => {
            //给输入框添加观察者，如果稍后数据更新会触发，会拿新的值给新的输入框
            fn(node, newVal)
        })
        fn(node, value);
        node.addEventListener('input', (e) => {
            let value = e.target.value;
            this.setVal(vm, expr, value);
        })
    },
    getContentVal(vm, expr) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(vm, args[1])
        })
    },
    html(node, expr, vm) {
        let fn = this.updater['htmlUpdater'];
        let value = this.getVal(vm, expr);
        new Watcher(vm, expr, (newVal) => {
            //给输入框添加观察者，如果稍后数据更新会触发，会拿新的值给新的输入框
            fn(node, newVal)
        })
        fn(node, value);
    },
    text(node, expr, vm) {
        let fn = this.updater['textUpdater'];
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            //console.log(args[1])
            //给表达式每个{{}}都加上观察者
            new Watcher(vm, args[1], (newVal) => {
                console.log(newVal)
                fn(node, this.getContentVal(vm, expr))//返回一个全的字符串
            })
            return this.getVal(vm, args[1])
        })
        fn(node, content)
    },
    updater: {
        modelUpdater(node, value) {
            node.value = value;
        },
        textUpdater(node, value) {
            node.textContent = value
        },
        htmlUpdater(node, value) {
            node.innerHTML = value
        }
    }
}

class Vue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        let computed = options.computed;
        let methods = options.methods;
        //这个跟元素存在
        if (this.$el) {
            console.log(this.$data,"| 数据属性");
            //把数据全部转换成 object.defineProperty 来定义
            new Observer(this.$data);
            //new Complie(this.$el,this);
            console.log(this.$data,"| 访问器属性")
            for (let key in computed) {//依赖关系 数据
                //console.log(computed[key].call(this.$data))
                //console.log(computed[key])
                Object.defineProperty(this.$data, key, {
                    get: () => {
                        return computed[key].call(this);
                    }
                })
            }
            for (let key in methods) {
                Object.defineProperty(this, key, {
                    get: () => {
                        return methods[key]
                    }
                })
            }
            //数据获取操作 vm上的取值操作 都代理到vm.$data
            this.proxyVm(this.$data)
            new Complie(this.$el, this);
        }

    }

    //代理 可以通过vm.school 取到 vm.$data.school
    proxyVm(data) {
        for (let key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key];//进行转换操作
                },
                set(newVal) {
                    return data[key] = newVal
                }
            })
        }
    }
}

// new Vue({el:'', data: {} , watch: '' , computed: {} , methods: {}})